package personal.vivian.builder;

import org.reflections.ReflectionUtils;
import personal.vivian.annontation.DataField;
import personal.vivian.annontation.DataModel;
import personal.vivian.interceptor.MethodInterc;
import personal.vivian.interf.Option;
import personal.vivian.interf.Options;
import personal.vivian.logic.DataManager;
import personal.vivian.logic.TemplatItem;
import personal.vivian.resover.Resovler;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;

public class TransformBuilder<T, C> {
    private Map<String, TemplatItem> itemMap = new HashMap<>();
    private Map<String, Field> fieldMap = new HashMap<>();
    private Class<T> model;
    private DataManager dataManager;
    private List<ItemBuilder<T, C>> itemBuilders = new ArrayList<>();
    private DataTemplate dataTemplate;

    public TransformBuilder(Class<T> model) {
        this.model = model;
    }

    public TransformBuilder(DataManager dataManager) {
        this.dataManager = dataManager;
    }

    public void init() {
        Set<Field> allFields = ReflectionUtils.getAllFields(model);
        for (Field field : allFields) {
            fieldMap.put(field.getName(), field);
        }
    }

    public static <T, C> TransformBuilder<T, C> create(Class<T> clazz, DataManager dataManager) {
        TransformBuilder<T, C> transformBuilder = new TransformBuilder<T, C>(dataManager);
        transformBuilder.setModel(clazz);
        return transformBuilder;
    }

    public TemplatItem getItem(String name, String fieldName, boolean create) {
        if (itemMap.containsKey(name)) {
            return itemMap.get(name);
        }
        TemplatItem item = new TemplatItem();
        if (fieldMap.containsKey(fieldName)) {
            Field field = fieldMap.get(fieldName);
            field.setAccessible(true);
            item.setTargetField(field);
            item.setName(name);
            itemMap.put(name, item);
        }
        return item;
    }

    public ItemBuilder<T, C> on(Function<T, Object> get, Option... options) {
        return on(get,null,options);
    }

    public ItemBuilder<T, C> on(Function<T, Object> get, TemplatItem item, Option... options) {
        init();
        MethodInterc methodInterc = new MethodInterc();
        T modell = (T) methodInterc.getInstance(model);
        get.apply(modell);
        MethodInfo methodInfo = methodInterc.getMethodInfo();
        String name = getName(methodInfo.getMethod().getName());
        ItemBuilder itemBuilder = buildItem(name, name, item, modell, options);
        return itemBuilder;
    }

    public DataTransformer buildTrans(DataManager dataManager) {
        DataTransformer dataTransformer = new DataTransformer();
        dataTransformer.setDataManager(dataManager);
        dataTransformer.addTemplate(this);
        return dataTransformer;

    }

    public ItemBuilder buildItem(String name, String field, TemplatItem item, T modell, Option... options) {
        ItemBuilder<T, C> i = new ItemBuilder<>(this, name, item, field, modell, options);
        itemBuilders.add(i);
        return i;
    }

    public DataTemplate buildTemplate() {
        DataTemplate dataTemplate1 = dataManager.getDataTemplate(model);
        if (dataTemplate1 != null) {
            return dataTemplate1;
        }
        dataTemplate = new DataTemplate();
        dataTemplate.setTemplateSource(model);
        scanModel(dataTemplate);

        for (ItemBuilder itemBuilder : itemBuilders) {
            itemBuilder.buildItem();
            dataTemplate.addItem(itemBuilder.getItem());
            List<Resovler> resovlers = itemBuilder.getItem().getResovlers();
            for(Resovler resovler:resovlers){
                resovler.apply(this, itemBuilder);
            }
            itemBuilder.buildItem();
        }

        dataManager.addTemplate(dataTemplate);
        return dataTemplate;

    }

    private void scanModel(DataTemplate dataTemplate) {
        DataModel dataModel = model.getAnnotation(DataModel.class);
        if (dataModel == null) {
            return;
        }
        dataTemplate.setName(dataModel.name());
        Set<Field> allFields = ReflectionUtils.getAllFields(model, f -> f.getAnnotation(DataField.class) != null);
        Map<String, TemplatItem> itemMap = new HashMap<>();
        for (Field f : allFields) {
            parseItem(f, dataTemplate, itemMap);
        }
    }

    private void parseItem(Field f, DataTemplate dataTemplate, Map<String, TemplatItem> itemMap) {
        TemplatItem item = null;
        DataField annotation = f.getAnnotation(DataField.class);
        String name = annotation.name();
        if (name == null) {
            name = f.getName();
        }
        item = new TemplatItem();
        item.setName(annotation.name());

        item.setTargetField(f);
        itemMap.put(name, item);
        item.setDataTemplate(dataTemplate);
        ItemBuilder itemBuilder = buildItem(name, name, item, (T) model, Options.editMode(annotation.editMode()),Options.val());
    }

    private String getName(String s) {
        if (s.startsWith("get")) {
            s = s.substring(3);
        } else if (s.startsWith("is")) {
            s = s.substring(2);
        }
        s = s.substring(0, 1).toLowerCase() + s.substring(1);
        return s;
    }

    public Map<String, TemplatItem> getItemMap() {
        return itemMap;
    }

    public void setItemMap(Map<String, TemplatItem> itemMap) {
        this.itemMap = itemMap;
    }

    public Map<String, Field> getFieldMap() {
        return fieldMap;
    }

    public void setFieldMap(Map<String, Field> fieldMap) {
        this.fieldMap = fieldMap;
    }

    public Class<?> getModel() {
        return model;
    }

    public void setModel(Class<T> model) {
        this.model = model;
    }

    public DataManager getDataManager() {
        return dataManager;
    }

    public void setDataManager(DataManager dataManager) {
        this.dataManager = dataManager;
    }

    public List<ItemBuilder<T, C>> getItemBuilders() {
        return itemBuilders;
    }

    public void setItemBuilders(List<ItemBuilder<T, C>> itemBuilders) {
        this.itemBuilders = itemBuilders;
    }

    public DataTemplate getDataTemplate() {
        return dataTemplate;
    }

    public void setDataTemplate(DataTemplate dataTemplate) {
        this.dataTemplate = dataTemplate;
    }
}
